package com.monochromebytes.mononet.test;

import java.io.IOException;
import java.net.InetSocketAddress;


import org.newdawn.slick.BasicGame;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Vector2f;

import com.monochromebytes.mononet.test.network.ServerNetworkController;


public class Server extends BasicGame {

    protected static int delta;
    
    private final SlickWindow slickWindow; 

    private final ServerNetworkController network;
    
    private Body body;
    private World world;

    public Server(InetSocketAddress localAddress) throws SlickException, IOException {
        super("Server - " + localAddress);
        this.network = new ServerNetworkController(localAddress);

        this.slickWindow = new SlickWindow(this);
    }

    @Override
    public void init(GameContainer container) throws SlickException {
        // Create World.
        this.world = World.getInstance();

        // Create Body.
        this.body = new Body();
        this.body.init(new Rectangle(300, 300, 50, 100),
            new Vector2f(0.2f, 0.2f),
            0.001f,
            1.0f);
        this.network.setBody(this.body);
    }

    @Override
    public void render(GameContainer container, Graphics g) {
        this.body.render(g);
        
        g.drawString("clients:  "+this.network.getNumberOfClients(), 20, 500);
        g.drawString("sent:     "+this.network.getNumberOfSentPackets(), 20, 520);
        g.drawString("received: "+this.network.getNumberOfReceivedPackets(), 20, 540);
        g.drawString("lost:     "+this.network.getNumberOfLostPackets(), 20, 560);
        g.drawString("ack:      "+this.network.getNumberOfAcknowledgedPackets(), 20, 580);
    }

    @Override
    public void update(GameContainer container, int delta) {
        Server.delta = delta;
        
        // Update body.
        this.body.update(delta);

        // Search for collisions.
        if (this.world.collidesWith(this.body)) {
            this.body.moveToLastShape();

            Collision collision = new Collision(this.world, this.body);

            // Handle collision.
            Body invader = (Body) collision.getInvader();
            Body victim = (Body) collision.getVictim();
            Vector2f intersection = collision.getInvaderIntersection();
            Vector2f normal = collision.getNormal();

            this.reactOnCollision(invader, victim, intersection, normal);
        }

        this.network.update(delta);
    }

    private void reactOnCollision(Body bodyA, Body bodyB, Vector2f PI, Vector2f n) {
        // Bodies angular frequencies
        float wA1 = bodyA.getAngularFrequency();
        float wB1 = bodyB.getAngularFrequency();

        // Bodies masses
        float mA = bodyA.getMass();
        float mB = bodyB.getMass();

        // Bodies velocities
        Vector2f vA1 = bodyA.getVelocity();
        Vector2f vB1 = bodyB.getVelocity();

        // Bodies center of mass
        Vector2f A = bodyA.getPosition();
        Vector2f B = bodyB.getPosition();

        // Vector from center of mass to point of collision P
        Vector2f AP = PI.copy().sub(A);
        Vector2f BP = PI.copy().sub(B);

        // Perpendicular vectors to AP and BP
        Vector2f APo = new Vector2f(-AP.y, AP.x);
        Vector2f BPo = new Vector2f(-BP.y, BP.x);

        // The relative velocity of P on both bodies
        Vector2f vAP1 = APo.copy().scale(wA1).add(vA1);
        Vector2f vBP1 = BPo.copy().scale(wB1).add(vB1);
        Vector2f vAB1 = vAP1.copy().sub(vBP1);

        // IA = m * rA² (moment of inertia of point A)
        float IA = bodyA.getInertia();
        float IB = bodyB.getInertia();

        // The coefficient of restitution
        float e = 1.0f;

        // -------------------------------

        // Torque
        // torque = (rAP * n)² / IA + (rBP * n)² / IB
        float torque = ((APo.dot(n) * APo.dot(n)) / IA) + ((BPo.dot(n) * BPo.dot(n)) / IB);

        // j = ( -(1+e)vAB * n ) / ( n * n(1/mA + 1/mB) + torque)
        float j = vAB1.copy().scale(-(1.0f + e)).dot(n)
                    // float j = (-(1.0f + e) * vAB1.dot(n))
            /
            (n.dot(n.copy().scale((1.0f / mA) + (1.0f / mB))) + torque);

        // vA2 = vA1 + (j/mA)*n
        Vector2f vA2 = vA1.copy().add(n.copy().scale(+j / mA));
        Vector2f vB2 = vB1.copy().add(n.copy().scale(-j / mB));

        bodyA.setVelocity(vA2);
        bodyB.setVelocity(vB2);

        // Torque
        // wA2 = wA1 + (rAPo * jn) / IA
        float wA2 = wA1 + (APo.dot(n.copy().scale(j)) / IA);
        float wB2 = wB1 - (BPo.dot(n.copy().scale(j)) / IB);

        bodyA.setAngularFrequency(wA2);
        bodyB.setAngularFrequency(wB2);
    }
    
    @Override
    public void keyPressed(int key, char c) {
        super.keyPressed(key, c);
        
        if (key == Input.KEY_R) {
            this.body.setPosition(300, 300);
        }
    }

    public SlickWindow getSlickWindow() {
        return slickWindow;
    }
}
